# encoding=utf-8
# socket通信服务端
# BingoLee 2024-04-29
import os
import sys
import time
import json
import socket
import struct
import signal
import threading
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT_DIR not in sys.path:
    sys.path.append(ROOT_DIR)

from utils.kit_profile import Profile
from utils.kit_utils import Log, Function

BIND_IP = '0.0.0.0'
SOCKET_PORT = 11000
HTTP_PORT = 11001
SOCKET = None


def signal_handle(signum, stack):
    Log.warn(f"----------Receive Signal {signum}, stop socket server-----------")
    if SOCKET is not None:
        SOCKET.close()
    exit()


def create_thread(func, args=()) -> threading.Thread:
    th = threading.Thread(target=func, args=args)
    th.daemon = True
    th.start()
    return th


class SocketServer(object):

    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind((BIND_IP, SOCKET_PORT))
        self.sock.listen(1)
        self.buf_size = 4096
        self.head_fmt = '16si800s'
        self.all_data = {}
        self.release_note = {}

    def start_listen(self):
        head_size = struct.calcsize(self.head_fmt)
        while 1:
            conn, addr = self.sock.accept()
            head = struct.unpack(self.head_fmt, conn.recv(head_size))
            oper = head[0].decode().rstrip('\0')
            data_size = head[1]
            param = head[2].decode().rstrip('\0')
            Log.info(f"{addr} {oper} {data_size} {param}")

            got_data = b''
            if data_size != 0:
                while 1:
                    got_data += conn.recv(self.buf_size)
                    if len(got_data) == data_size:
                        break
            Log.info(f"got data size: {len(got_data)}")
            try:
                self.handle(conn, oper, param, got_data)
            except Exception as e:
                Log.error(f"Handle error: {str(e)}")

    def close(self):
        try:
            self.sock.close()
        except:
            pass

    def send_back_data(self, conn, back_param: str, back_data: str):
        """
        回复的数据
        """
        back_data = back_data.encode()
        data_size = len(back_data)
        head = struct.pack(self.head_fmt, b'', data_size, back_param.encode())
        conn.sendall(head)
        if not data_size:
            return
        count = 0
        while data_size >= self.buf_size:
            conn.sendall(back_data[count * self.buf_size: (count + 1) * self.buf_size])
            data_size -= self.buf_size
            count += 1
        conn.sendall(back_data[-data_size:])

    def handle(self, conn, oper: str, param: str, data: bytes):
        def impl(*args):
            if oper == 'ALL_DATA':
                self.send_back_data(conn, 'ALL_DATA', json.dumps(self.all_data))
            elif oper == 'RELEASE_NOTE':
                self.send_back_data(conn, 'RELEASE_NOTE', json.dumps(self.release_note))
            else:
                self.send_back_data(conn, f"Not support {oper}", '')

        create_thread(impl)

    def update_data_thread(self):
        """ 开启线程更新本地数据文件到内存，并将数据返回给客户端 """
        def impl(*args):
            while True:
                try:
                    all_data, err = Function.json_read(Profile.server_data_file)
                    if all_data is None:
                        Log.error(f"update server all data thread error: {str(err)}")
                except Exception as e:
                    Log.error(f"update server all data thread exception: {str(e)}")
                    all_data = {}
                if all_data:
                    self.all_data = all_data
                try:
                    release_note, err = Function.json_read(Profile.server_release_note)
                    if release_note is None:
                        Log.error(f"update server release note thread error: {str(err)}")
                except Exception as e:
                    Log.error(f"update server release note thread exception: {str(e)}")
                    release_note = {}
                if release_note:
                    self.release_note = release_note

                time.sleep(10)

        create_thread(impl)


def main():
    global SOCKET
    signal.signal(signal.SIGTERM, signal_handle)

    # http server for static files
    static_dir = os.path.join(ROOT_DIR, 'static')
    os.makedirs(static_dir, exist_ok=True)
    os.chdir(static_dir)
    if os.name == 'nt':
        os.system(f"start cmd /k python -m http.server {HTTP_PORT}")
    else:
        os.system(f"nohup python{sys.version_info.major}.{sys.version_info.minor} -m http.server {HTTP_PORT} &")

    # socket server
    SOCKET = server = SocketServer()
    server.update_data_thread()
    server.start_listen()


if __name__ == '__main__':
    main()
